/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2013.12.10                                          *
 *                                                                       *
 *************************************************************************/

#ifndef FreeMAPs_include_tbox_Database_h
#define FreeMAPs_include_tbox_Database_h

#include "FreeMAPsConfig.h"

//C++ headers
#include <list>
#include <vector>
#include <boost/shared_ptr.hpp>

//COMMON headers
#include "COMMON/base/Common.h"
#include "COMMON/enum/DataType.h"
#include "COMMON/tbox/DatabaseBox.h"
#include "COMMON/tbox/Complex.h"

namespace FreeMAPs
{

/**
 * Class Database stores (key,value) pairs in a hierarchical
 * database.  Each value may be another database, boolean, box, character,
 * complex, double, float, integer, or string.  Note that boxes are stored
 * using the toolbox box class that can store boxes of any dimension in the
 * same data structure.
 *
 * See the Database class documentation for a description of the
 * generic database interface.
 *
 * The memory database is used for storing input (a "deck") in memory; the
 * derived class Input_database use for this purpose; Input_database is
 * kept around for historical reasons and the name is a bit more clear
 * than "memory" database but this class is very generic.  The Parser
 * is really the class that process the input; it parses the input file
 * into a database.
 *
 * Note that the memory database will attempt to promote numerical types
 * where appropriate.  The promotion chain is int -> float -> double ->
 * complex.  For example, an integer key will be promoted to a complex
 * value if is_complex() or get_complex() is called.  Double values will also
 * be truncated to floats (with loss of information) if a float call is
 * made on a double value.
 *
 * It is assumed that all processors will access the database in the same
 * manner.  Thus, all error messages are output to POUT instead of perr.
 */

class Database
{
public:
    /**
     * The memory database constructor creates an empty database with the
     * specified name.
     */
    explicit
    Database(
        const std::string& name);

    /**
     * The memory database destructor deallocates the data in the database.
     */
    virtual
    ~Database();

public:

    /**
     * Create a new database file.
     *
     * Returns true if successful.
     *
     * @param name name of database. Normally a filename.
     */
    virtual bool
    create(
        const std::string& name);

    /**
     * Open an existing database file.
     *
     * Returns true if successful.
     *
     * @param name name of database. Normally a filename.
     *
     * @param read_write_mode Open the database in read-write
     * mode instead of read-only mode.  NOTE: This class currently
     * does not support read-only mode, so this flag must be true.
     *
     * @pre read_write_mode == true
     */
    virtual bool
    open(
        const std::string& name,
        const bool read_write_mode = false);

    /**
     * Close the database.
     *
     * Returns true if successful.
     *
     * If the database is currently open then close it.  This should
     * flush all data to the file (if the database is on disk).
     */
    virtual bool
    close();

    /**
     * Return string name of memory database object.
     */
    virtual std::string
    get_name() const;

    /**
     * Return true if the specified key exists in the database and false
     * otherwise.
     */
    virtual bool
    key_exists(
        const std::string& key);

    /**
     * Return all keys in the database.
     */
    virtual std::vector<std::string>
    get_all_keys();

    /**
     * @brief Return the type of data associated with the key.
     *
     * If the key does not exist, then INVALID is returned
     *
     * @param key Key name in database.
     */
    virtual DataType
    get_array_type(
        const std::string& key);

    /**
     * Return the size of the array associated with the key.  If the key
     * does not exist, then zero is returned.
     */
    virtual size_t
    get_array_size(
        const std::string& key);

    /**
     * Return whether the specified key represents a database entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_database(
        const std::string& key);

    /**
     * Create a new database with the specified key name.  If the key already
     * exists in the database, then the old key record is deleted and the new
     * one is silently created in its place.
     */
    virtual boost::shared_ptr<Database>
    put_database(
        const std::string& key);

    /**
     * Get the database with the specified key name.  If the specified
     * key does not exist in the database or it is not a database, then
     * an error message is printed and the program exits.
     */
    virtual boost::shared_ptr<Database>
    get_database(
        const std::string& key);

    /**
     * Return whether the specified key represents a boolean entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_bool(
        const std::string& key);

    /**
     * Create a boolean scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_bool(
        const std::string& key,
        const bool& data);

    /**
     * Create a boolean vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     * note that vector<bool> has a not of problem. so use vector<int> instead.
     */
    void
    put_bool_vector(
        const std::string& key,
        const std::vector<int>& data);

    /**
     * Create a boolean array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_bool_array(
        const std::string& key,
        const bool * const data,
        const size_t nelements);

    /**
     * Get a boolean entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not a
     * boolean scalar, then an error message is printed and the program
     * exits.
     */
    virtual bool
    get_bool(
        const std::string& key);

    /**
     * Get a boolean entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a boolean scalar,
     * then an error message is printed and the program exits.
     */
    virtual bool
    get_bool_with_default(
        const std::string& key,
        const bool& defaultvalue);

    /**
     * Get a boolean entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a boolean vector, then an error message is printed and
     * the program exits.
     */
    virtual std::vector<bool>
    get_bool_vector(
        const std::string& key);

    /**
     * Get a boolean entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a boolean array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     */
    virtual void
    get_bool_array(
        const std::string& key,
        bool* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a box entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_database_box(
        const std::string& key);

    /**
     * Create a box scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_database_box(
        const std::string& key,
        const DatabaseBox& data);

    /**
     * Create a box vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_database_box_vector(
        const std::string& key,
        const std::vector<DatabaseBox>& data);

    /**
     * Create a box array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_database_box_array(
        const std::string& key,
        const DatabaseBox * const data,
        const size_t nelements);

    /**
     * Get a box entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not a
     * box scalar, then an error message is printed and the program
     * exits.
     */
    virtual DatabaseBox
    get_database_box(
        const std::string& key);

    /**
     * Get a box entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a box scalar,
     * then an error message is printed and the program exits.
     */
    virtual DatabaseBox
    get_database_box_with_default(
        const std::string& key,
        const DatabaseBox& defaultvalue);

    /**
     * Get a box entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a box vector, then an error message is printed and
     * the program exits.
     *
     * @param key Key name in database.
     */
    virtual std::vector<DatabaseBox>
    get_database_box_vector(
        const std::string& key);

    /**
     * Get a box entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a box array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     */
    virtual void
    get_database_box_array(
        const std::string& key,
        DatabaseBox* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a character entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_char(
        const std::string& key);

    /**
     * Create a character scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_char(
        const std::string& key,
        const char& data);

    /**
     * Create a character vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_char_vector(
        const std::string& key,
        const std::vector<char>& data);

    /**
     * Create a character array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_char_array(
        const std::string& key,
        const char * const data,
        const size_t nelements);

    /**
     * Get a character entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not an
     * character scalar, then an error message is printed and the program
     * exits.
     */
    virtual char
    get_char(
        const std::string& key);

    /**
     * Get a character entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a character scalar,
     * then an error message is printed and the program exits.
     */
    virtual char
    get_char_with_default(
        const std::string& key,
        const char& defaultvalue);

    /**
     * Get a character entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a character vector, then an error message is printed and
     * the program exits.
     */
    virtual std::vector<char>
    get_char_vector(
        const std::string& key);

    /**
     * Get a character entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a character array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     */
    virtual void
    get_char_array(
        const std::string& key,
        char* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a complex entry.  If
     * the key does not exist, then false is returned.  _complex values
     * may be promoted from integers, floats, or doubles.
     */
    virtual bool
    is_complex(
        const std::string& key);

    /**
     * Create a complex scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_complex(
        const std::string& key,
        const Complex& data);

    /**
     * Create a complex vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_complex_vector(
        const std::string& key,
        const std::vector<Complex>& data);

    /**
     * Create a complex array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_complex_array(
        const std::string& key,
        const Complex * const data,
        const size_t nelements);

    /**
     * Get a complex entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not a
     * complex scalar, then an error message is printed and the program
     * exits.  _complex values may be promoted from integers, floats, or
     * doubles.
     */
    virtual Complex
    get_complex(
        const std::string& key);

    /**
     * Get a complex entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a complex scalar,
     * then an error message is printed and the program exits.  _complex
     * values may be promoted from integers, floats, or doubles.
     */
    virtual Complex
    get_complex_with_default(
        const std::string& key,
        const Complex& defaultvalue);

    /**
     * Get a complex entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a complex vector, then an error message is printed and
     * the program exits.  _complex values may be promoted from integers,
     * floats, or doubles.
     */
    virtual std::vector<Complex>
    get_complex_vector(
        const std::string& key);

    /**
     * Get a complex entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a complex array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     * _complex values may be promoted from integers, floats, or doubles.
     */
    virtual void
    get_complex_array(
        const std::string& key,
        Complex* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a double entry.  If
     * the key does not exist, then false is returned.  Double values
     * may be promoted from integers or floats.
     */
    virtual bool
    is_double(
        const std::string& key);

    /**
     * Create a double scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_double(
        const std::string& key,
        const double& data);

    /**
     * Create a double vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_double_vector(
        const std::string& key,
        const std::vector<double>& data);

    /**
     * Create a double array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_double_array(
        const std::string& key,
        const double * const data,
        const size_t nelements);

    /**
     * Get a double entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not a
     * double scalar, then an error message is printed and the program
     * exits.  Double values may be promoted from integers or floats.
     */
    virtual double
    get_double(
        const std::string& key);

    /**
     * Get a double entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a double scalar, then
     * an error message is printed and the program exits.  Double values may
     * be promoted from integers or floats.
     */
    virtual double
    get_double_with_default(
        const std::string& key,
        const double& defaultvalue);

    /**
     * Get a double entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a double vector, then an error message is printed and
     * the program exits.  Double values may be promoted from integers
     * or floats.
     */
    virtual std::vector<double>
    get_double_vector(
        const std::string& key);

    /**
     * Get a double entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a double array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     * Double values may be promoted from integers or floats.
     */
    virtual void
    get_double_array(
        const std::string& key,
        double* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a float entry.  If
     * the key does not exist, then false is returned.  _float values
     * may be promoted from integers or silently truncated from doubles.
     */
    virtual bool
    is_float(
        const std::string& key);

    /**
     * Create a float scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_float(
        const std::string& key,
        const float& data);

    /**
     * Create a float vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_float_vector(
        const std::string& key,
        const std::vector<float>& data);

    /**
     * Create a float array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_float_array(
        const std::string& key,
        const float * const data,
        const size_t nelements);

    /**
     * Get a float entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not a
     * float scalar, then an error message is printed and the program
     * exits.  _float values may be promoted from integers or silently
     * truncated from doubles.
     */
    virtual float
    get_float(
        const std::string& key);

    /**
     * Get a float entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a float scalar, then
     * an error message is printed and the program exits.  _float values may
     * be promoted from integers or silently truncated from doubles.
     */
    virtual float
    get_float_with_default(
        const std::string& key,
        const float& defaultvalue);

    /**
     * Get a float entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a float vector, then an error message is printed and
     * the program exits.  _float values may be promoted from integers
     * or silently truncated from doubles.
     */
    virtual std::vector<float>
    get_float_vector(
        const std::string& key);

    /**
     * Get a float entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a float array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     * _float values may be promoted from integers or silently truncated
     * from doubles.
     */
    virtual void
    get_float_array(
        const std::string& key,
        float* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents an integer entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_integer(
        const std::string& key);

    /**
     * Create an integer scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_integer(
        const std::string& key,
        const int& data);

    /**
     * Create an integer vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_integer_vector(
        const std::string& key,
        const std::vector<int>& data);

    /**
     * Create an integer array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_integer_array(
        const std::string& key,
        const int * const data,
        const size_t nelements);

    /**
     * Get an integer entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not an
     * integer scalar, then an error message is printed and the program
     * exits.
     */
    virtual int
    get_integer(
        const std::string& key);

    /**
     * Get an integer entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not an integer scalar,
     * then an error message is printed and the program exits.
     */
    virtual int
    get_integer_with_default(
        const std::string& key,
        const int& defaultvalue);

    /**
     * Get an integer entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not an integer vector, then an error message is printed and
     * the program exits.
     */
    virtual std::vector<int>
    get_integer_vector(
        const std::string& key);

    /**
     * Get an integer entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not an integer array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     */
    virtual void
    get_integer_array(
        const std::string& key,
        int* data,
        const size_t nelements);

    /**
     * Return whether the specified key represents a std::string entry.  If
     * the key does not exist, then false is returned.
     */
    virtual bool
    is_string(
        const std::string& key);

    /**
     * Create a string scalar entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_string(
        const std::string& key,
        const std::string& data);

    /**
     * Create a string vector entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_string_vector(
        const std::string& key,
        const std::vector<std::string>& data);

    /**
     * Create a string array entry in the database with the specified
     * key name.  If the key already exists in the database, then the old
     * key record is deleted and the new one is silently created in its place.
     */
    virtual void
    put_string_array(
        const std::string& key,
        const std::string * const data,
        const size_t nelements);

    /**
     * Get a string entry in the database with the specified key name.
     * If the specified key does not exist in the database or is not an
     * string scalar, then an error message is printed and the program
     * exits.
     */
    virtual std::string
    get_string(
        const std::string& key);

    /**
     * Get a string entry in the database with the specified key name.
     * If the specified key does not exist in the database, then the default
     * value is returned.  If the key exists but is not a string scalar,
     * then an error message is printed and the program exits.
     */
    virtual std::string
    get_string_with_default(
        const std::string& key,
        const std::string& defaultvalue);

    /**
     * Get a string entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a string vector, then an error message is printed and
     * the program exits.
     */
    virtual std::vector<std::string>
    get_string_vector(
        const std::string& key);

    /**
     * Get a string entry from the database with the specified key
     * name.  If the specified key does not exist in the database or
     * is not a string array, then an error message is printed and
     * the program exits.  The specified number of elements must match
     * exactly the number of elements in the array in the database.
     */
    virtual void
    get_string_array(
        const std::string& key,
        std::string* data,
        const size_t nelements);

    /**
     * @brief Returns the name of this database.
     *
     * The name for the root of the database is the name supplied when creating
     * it.  Names for nested databases are the keyname of the database.
     */
    virtual std::string
    get_name();

    /**
     * Return whether the specified key has been accessed by one of the
     * lookup member functions.  If the key does not exist in the database,
     * then false is returned.
     */
    inline bool
    key_accessed(
        const std::string& key);

    /**
     * Print the current database to the specified output stream.  After
     * each key, print whether that key came from the a file and was
     * used, came from the file but was not used (unused),
     * or came from a default key value (default).  If no output stream
     * is specified, then data is written to stream POUT.
     *
     * NOTE:  under the g++ compiler libraries, printClassData has a
     * maximum output of 4096 characters per line.
     */
    virtual void
    print_class_data(
        std::ostream& os);

    /**
     * Print the database keys that were not used to the specified output
     * stream.
     */
    inline void
    print_unused_keys(
        std::ostream& os) const;

    /**
     * Print the database keys that were set via default calls to the specified
     * output stream.
     */
    inline void
    print_default_keys(
        std::ostream& os) const;

private:

    /*
     * The following structure holds the list of (key,value) pairs stored
     * in the database.  Note that only one of the arrays contains valid
     * data for any particular key.
     */
    struct KeyData {
        std::string d_key;                                // key name
        enum DataType d_type;                  // type of entry
        size_t d_array_size;                              // size of array data
        bool d_accessed;                                  // whether accessed
        bool d_from_default;                              // from default key
        boost::shared_ptr<Database> d_database;           // sub-database
        std::vector<bool> d_boolean;              // boolean vector value
        std::vector<DatabaseBox> d_box;           // box vector value
        std::vector<char> d_char;                 // char vector value
        std::vector<Complex> d_complex;           // complex vector value
        std::vector<double> d_double;             // double vector value
        std::vector<float> d_float;               // float vector value
        std::vector<int> d_integer;               // integer vector value
        std::vector<std::string> d_string;        // string vector value
    };

    /*
     * Private utility routines for managing the database
     */
    bool
    delete_key_if_found(
        const std::string& key);

    KeyData *
    find_key_data(
        const std::string& key);

    KeyData *
    find_key_data_or_exit(
        const std::string& key);

    static void
    indent_stream(
        std::ostream& os,
        const long indent)
    {
        for (int i = 0; i < indent; ++i) {
            os << " ";
        }
    }

    void
    print_database(
        std::ostream& os,
        const int indent,
        const int toprint) const;

private:

    /*
     * Private data members - name and a list of (key,value) pairs
     */
    std::string d_database_name;
    std::list<KeyData> d_keyvalues;

    static const int PRINT_DEFAULT;
    static const int PRINT_INPUT;
    static const int PRINT_UNUSED;
    static const int SSTREAM_BUFFER;

private:

    DISALLOW_COPY_AND_ASSIGN (Database);
};

}

#include "COMMON/tbox/Database.inl"

#endif

